package dynamojunit;

import java.util.HashMap;
import java.util.Map;

import Common.TraceWriter;
import cloudjunit.Config;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.dynamodb.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodb.model.*;

public class DynamoDbCommon
{
	public static final String INITIAL = "INITIAL"; 
	public static final String RUNNING = "RUNNING";
	public static final String FINISHED = "FINISHED";
	
	public static AmazonDynamoDBClient dynamoDB = Config.createDynamoDBClient();
	
	public static void initTable(String tableName, String primaryKeyName)
	{
		try
		{
			if (!DynamoDbCommon.isTableExist(tableName))
			{
				TraceWriter.trace("Table doesn't exist. Creating Table "
						+ tableName + " ...");
				createTable(tableName, primaryKeyName);
			}
			else TraceWriter.trace("Table exist: " + tableName);

			printDescribe(tableName);

		} catch (AmazonServiceException ase) {
			TraceWriter.trace("Caught an AmazonServiceException, which means your request made it "
							+ "to AWS, but was rejected with an error response for some reason.");
			TraceWriter.trace("Error Message:    " + ase.getMessage());
			TraceWriter.trace("HTTP Status Code: " + ase.getStatusCode());
			TraceWriter.trace("AWS Error Code:   " + ase.getErrorCode());
			TraceWriter.trace("Error Type:       " + ase.getErrorType());
			TraceWriter.trace("Request ID:       " + ase.getRequestId());
			throw ase;
		} catch (AmazonClientException ace) {
			System.out
					.println("Caught an AmazonClientException, which means the client encountered "
							+ "a serious internal problem while trying to communicate with AWS, "
							+ "such as not being able to access the network.");
			TraceWriter.trace("Error Message: " + ace.getMessage());
			throw ace;
		}
	}

	public static void printDescribe(String tableName)
	{
		DescribeTableRequest describeTableRequest = new DescribeTableRequest()
				.withTableName(tableName);
		TableDescription tableDescription = dynamoDB.describeTable(
				describeTableRequest).getTable();
		TraceWriter.trace("Table Description: " + tableDescription);
	}
	
    public static boolean isTableExist(String tableName)
    {
    	try
    	{
	        DescribeTableRequest describeTableRequest = new DescribeTableRequest().withTableName(tableName);
	        dynamoDB.describeTable(describeTableRequest).getTable();
	        return true;
    	}
    	catch (AmazonServiceException e)
    	{
    		if (e.getStatusCode() == 400)	// resource not found exception
   		 		return false;
    		else
    			throw e;
    	}
    }
    
    public static void createTable(String tableName, String primaryKeyName)
    {
        // Create a table with a primary key named 'name', which holds a string
        CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
            .withKeySchema(new KeySchema(new KeySchemaElement().withAttributeName(primaryKeyName).withAttributeType("S")))
            .withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(5L).withWriteCapacityUnits(5L));
        TableDescription createdTableDescription = dynamoDB.createTable(createTableRequest).getTableDescription();
        TraceWriter.trace("Created Table: " + createdTableDescription);

        // Wait for it to become active
        waitForTableToBecomeAvailable(tableName);
    }
    
    ////////////////////////////////////
    
	public static Map<String,AttributeValue> readUniqueEntry(String tableName, String keyName, String keyValue)
	{
        HashMap<String, Condition> scanFilter = new HashMap<String, Condition>();
        Condition condition = new Condition()
            .withComparisonOperator(ComparisonOperator.EQ.toString())
            .withAttributeValueList(new AttributeValue(keyValue));
        scanFilter.put(keyName, condition);
		
        ScanRequest scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter);
        ScanResult scanResult = dynamoDB.scan(scanRequest);
        TraceWriter.trace("Result: " + scanResult);
        assert(scanResult.getCount() <= 1);		// key should provide uniqueness
        return scanResult.getItems().get(0);	// assumes entry exists
	}
	
	public static boolean isMatchingEntryExist(String tableName, String keyName, String keyValue)
	{
        HashMap<String, Condition> scanFilter = new HashMap<String, Condition>();
        Condition condition = new Condition()
            .withComparisonOperator(ComparisonOperator.EQ.toString())
            .withAttributeValueList(new AttributeValue(keyValue));
        scanFilter.put(keyName, condition);
		
        ScanRequest scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter);
        ScanResult scanResult = dynamoDB.scan(scanRequest);
        assert(scanResult.getCount() <= 1);		// key should provide uniqueness
        return scanResult.getCount() > 0;
	}
    
	public static void putItemWithConsistency(String tableName, String keyName, Map<String, AttributeValue> item)
	{
		PutItemRequest putItemRequest = new PutItemRequest(tableName, item);
		PutItemResult putItemResult = dynamoDB.putItem(putItemRequest);
		TraceWriter.trace("putItem Result: " + putItemResult);

		// Add consistency
		boolean wasInserted = false;		
		for (int tryNum = 0; tryNum < 5; ++tryNum)
		{
			if ( isMatchingEntryExist(tableName, keyName, item.get(keyName).getS()) )
			{
				wasInserted = true;
				break;
			}
			try { Thread.sleep(100); } catch(Exception e) {}
		}
		if (!wasInserted)
		{
			TraceWriter.trace("putItem failed");
			throw new RuntimeException("putItem was not able to insert to table " + tableName);
		}
	}
	
	public static void updateItem(String tableName, Key key, Map<String, AttributeValueUpdate> item)
	{	
		UpdateItemRequest updaetItemRequest = new UpdateItemRequest(tableName, key, item);
		UpdateItemResult updateItemResult = dynamoDB.updateItem(updaetItemRequest);
		TraceWriter.trace("updateItem Result: " + updateItemResult);
	}
	
	////////////////////////////////////
	
	public static String convertTestClassToTCID(String testClassName, String wfid)
	{
		// Unique ID for runnning testClass in a workFlow
		return wfid + "#" + testClassName;
	}
	
	////////////////////////////////////
    
    private static void waitForTableToBecomeAvailable(String tableName)
    {
    	// Taken from AmaconDynamoDBSample.java
    	
        TraceWriter.trace("Waiting for " + tableName + " to become ACTIVE...");

        long startTime = System.currentTimeMillis();
        long endTime = startTime + (10 * 60 * 1000);
        while (System.currentTimeMillis() < endTime)
        {
            try {Thread.sleep(1000 * 10);} catch (Exception e) {}
            try
            {
                DescribeTableRequest request = new DescribeTableRequest().withTableName(tableName);
                TableDescription tableDescription = dynamoDB.describeTable(request).getTable();
                String tableStatus = tableDescription.getTableStatus();
                TraceWriter.trace("  - current state: " + tableStatus);
                if (tableStatus.equals(TableStatus.ACTIVE.toString())) return;
            }
            catch (AmazonServiceException ase)
            {
                if (ase.getErrorCode().equalsIgnoreCase("ResourceNotFoundException") == false) throw ase;
            }
        }

        throw new RuntimeException("Table " + tableName + " never went active");
    }
    
    public static AttributeValue readValuefromMap(Map<String,AttributeValue> entry_map, String value_key)
	{
		for (Map.Entry<String, AttributeValue> entry: entry_map.entrySet())
		{
			if (entry.getKey()==value_key)
			{
				return entry.getValue();
			}
		}
		return null;
	}
	
}
